// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//


#import "WW3DLight.h"
#import "EveCommand.h"
#import "WW3DAttributeState.h"
#import "usefulWW3DFunctions.h"

@implementation WW3DLight


#define wwPI  	    (3.1415926535897932384626433)
#define DtoR 		(wwPI/180.0)
#define RtoD 		(180.0/wwPI)
#define	toDegrees(r)	((r)*RtoD)
#define	toRadians(d)	((d)*DtoR)
#define X_AXIS      1.0, 0.0, 0.0
#define Y_AXIS      0.0, 1.0, 0.0
#define Z_AXIS      0.0, 0.0, 1.0


+ initialize {  [WW3DLight setVersion:1]; return self; }

- init
{
  [super init];
  visibleSizeScale = 1.0;
  visibleWhenPrinting = NO;

  xColor[0] = 1.0;
  xColor[1] = 0.0;
  xColor[2] = 0.0;
  yColor[0] = 0.0;
  yColor[1] = 1.0;
  yColor[2] = 0.0;
  zColor[0] = 0.0;
  zColor[1] = 0.0;
  zColor[2] = 1.0;

  innerConeOpacity[0] = 0.3;
  innerConeOpacity[1] = 0.3;
  innerConeOpacity[2] = 0.3;
  outerConeOpacity[0] = 0.1;
  outerConeOpacity[1] = 0.1;
  outerConeOpacity[2] = 0.1;
  beamOpacity[0] = intensity;
  beamOpacity[1] = intensity;
  beamOpacity[2] = intensity;

  xRotate = 0.0;
  yRotate = 0.0;

  NXConvertColorToRGB(color, &myRtColor[0], &myRtColor[1], &myRtColor[2]); 

  return self;
}

- awake
{
  [super awake];
  NXConvertColorToRGB(color, &myRtColor[0], &myRtColor[1], &myRtColor[2]); 
  return self;
}

- copyFromZone:(NXZone *)zone  {  return [super copyFromZone:zone]; }

- synchUpRotationValues
{
  v[0] = from[0] - to[0];
  v[1] = from[1] - to[1];
  v[2] = from[2] - to[2];
  vMagnitude = sqrt((double)((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])));

  u[0] = v[0]/vMagnitude;
  u[1] = v[1]/vMagnitude;
  u[2] = v[2]/vMagnitude;

  if (u[2] != 0.0)
  {  yRotate = toDegrees(((RtFloat)atan((double)(u[0]/u[2]))));
     xRotate = toDegrees(( -1. * (RtFloat)atan((double)(u[1]/u[2]))));
  }
  else
  {  yRotate = toDegrees(((RtFloat)atan((double)(u[0]))));
     xRotate = toDegrees((-1. * (RtFloat)atan((double)(u[1]))));
  }

//  NXLogError("from == (%f, %f, %f) : to== (%f, %f, %f)\n", 
//             from[0], from[1], from[2], to[0], to[1], to[2]);
//  NXLogError("u == (%f, %f, %f) : xRotate == %f : yRotate == %f\n", 
//             u[0], u[1], u[2], xRotate, yRotate);

  return self;
}

- (BOOL)on  {  return lightFlags.on;  }

- setColor:(NXColor)aColor
{
  [super setColor:aColor];
  NXConvertColorToRGB(color, &myRtColor[0], &myRtColor[1], &myRtColor[2]); 
  return self;
}

- ribCommands { return ribCommandList; }
- appendRIBCommand:newRIBCommand
{
    if ([newRIBCommand isKindOf:[EveCommand class]]) {
	hasEveCmd = YES;
    }
    [ribCommandList addObject:newRIBCommand];
    dirtyBoundingBox = YES;
    return self;
}


- renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];


  [super renderSelf:camera];

  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }

  return self;
}

- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtFloat  length;
  int      i, howMany = [ribCommandList count];


  [super renderSelf:camera];


  // uncomment this eventually, otherwise we'll always render the lights...
  //if ((NXDrawingStatus != NX_DRAWING) && !visibleWhenPrinting)
  //{  return self;
  //}

  RiAttributeBegin(); // this geometry and state that we don't really
                      // want to foist on the light shader that this shape 
                      // actually represents, so we wrap all the following 
                      // in an AttributeBegin/End block.

  RiSurface("matte", RI_NULL);  // force a reasonable, cheap shader
  // need to know if it's selected or not - if it is, draw as selected color otherwise use own color
  // for now, just color it...
  RiColor(myRtColor);

   switch (type)  
   {  case N3D_AmbientLight:  // an ambient light is just a colored sphere of diameter 1
                              RiSphere(visibleSizeScale * .5, visibleSizeScale * -.5, visibleSizeScale * .5, 360.0, RI_NULL);
                              break;
      case N3D_PointLight:    // a point light is six pointed star
                              RiTranslate(from[0], from[1], from[2]);
                              RiSphere(visibleSizeScale * .25, visibleSizeScale * -.25, visibleSizeScale * .25, 360.0, RI_NULL);

                              RiColor(zColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * .4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * -0.4);
                              RiRotate(-90, X_AXIS);
                              RiColor(yColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * 0.4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * -0.4);
                              RiRotate(-90, X_AXIS);
                              RiColor(zColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * 0.4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * -0.4);

                              RiRotate(-90, X_AXIS);
                              RiColor(yColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * 0.4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * -0.4);

                              RiRotate(-90, X_AXIS);
                              RiRotate(90, Y_AXIS);
                              RiColor(xColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * 0.4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * -0.4);

                              RiRotate(180, Y_AXIS);
                              RiColor(xColor);
                              RiCylinder(visibleSizeScale * .05, 0, visibleSizeScale * .4, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * 0.4);
                              RiCone(visibleSizeScale * .1, visibleSizeScale * .1, 360, RI_NULL);
	                      break;
      case N3D_DistantLight:  // this looks like a normalized arrow
                              RiTranslate(from[0], from[1], from[2]);
                              RiRotate(yRotate, Y_AXIS);
                              RiRotate(xRotate, X_AXIS);

                              RiRotate(180, Y_AXIS);  // flip around to point Z at toPoint
                              RiCylinder(visibleSizeScale * .1, 0, visibleSizeScale * .8, 360, RI_NULL);
                              RiTranslate(0, 0, visibleSizeScale * .8);
                              RiCone(visibleSizeScale * .2, visibleSizeScale * .2, 360, RI_NULL);
                              break;
      case N3D_SpotLight:     // two cones and an arrow pointing out
                              RiTranslate(from[0], from[1], from[2]);
                              RiRotate(yRotate, Y_AXIS);
                              RiRotate(xRotate, X_AXIS);
                              RiTranslate(0.0, 0.0, -1.0 * visibleSizeScale);

                              RiOpacity(outerConeOpacity);
                              RiCone(visibleSizeScale,  visibleSizeScale * tan((double)((wwPI/180.0) * coneAngle)), 360, RI_NULL);
                              RiOpacity(innerConeOpacity);
                              RiCone(visibleSizeScale,  visibleSizeScale * tan((double)((wwPI/180.0) * (coneAngle - coneDelta))), 360, RI_NULL);

                              // need to represent the beam distribution
                              RiOpacity(beamOpacity);
                              RiTranslate(0.0, 0.0, visibleSizeScale);
                              RiRotate(180, Y_AXIS);  // flip around to point Z at toPoint
                              length = (beamDistribution * visibleSizeScale * .8);
                              RiCylinder(visibleSizeScale * .01, 0, length, 360, RI_NULL);
                              RiTranslate(0, 0, length);
                              RiCone(length * .2, length * .05, 360, RI_NULL);
                              break;
      default:                // complain
	                      break;
  }
  RiAttributeEnd();
  
  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }

  return self;
}

- renderSelf:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- makePointFrom:(RtPoint)pf intensity:(RtFloat)i
{
  [super makePointFrom:pf intensity:i];
  beamOpacity[0] = intensity;
  beamOpacity[1] = intensity;
  beamOpacity[2] = intensity;
  return [self synchUpRotationValues];
}

- makeDistantFrom:(RtPoint)pf to:(RtPoint)pt intensity:(RtFloat)i
{
  [super makeDistantFrom:pf to:pt intensity:i];
  beamOpacity[0] = intensity;
  beamOpacity[1] = intensity;
  beamOpacity[2] = intensity;
  return [self synchUpRotationValues];
}

- makeSpotFrom:(RtPoint)pf to:(RtPoint)pt
		coneAngle:(RtFloat)ca coneDelta:(RtFloat)cd
		beamDistribution:(RtFloat)bd
		intensity:(RtFloat)i
{
  [super makeSpotFrom:pf to:pt	coneAngle:ca coneDelta:cd beamDistribution:bd intensity:i];
  beamOpacity[0] = intensity;
  beamOpacity[1] = intensity;
  beamOpacity[2] = intensity;
  return [self synchUpRotationValues];
   
}


- setFrom:(RtPoint)pf
{
  [super setFrom:pf];
  return [self synchUpRotationValues];
}


- setFrom:(RtPoint)pf to:(RtPoint)pt
{
  [super setFrom:pf to:pt];
  return [self synchUpRotationValues];
}

- setIntensity:(RtFloat)i
{
  [super setIntensity:i];
  beamOpacity[0] = intensity;
  beamOpacity[1] = intensity;
  beamOpacity[2] = intensity;
  return [self synchUpRotationValues];
}

- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{  NXLogError("why are you calling transformCTM:startingAt:endingAt: for WW3DShape <%s>?\n", [self shapeName]);
   return self;
}

- (BOOL)isSelectable { return YES; } // This is a N3DKit thing, not mine

- (BOOL)isLerpable { return NO; }
- lerpWith:b by:(float)uValue { return self; }
- lerpSelfWith:b by:(float)uValue { return self; }

- (BOOL)isMotionBlurrable { return YES; }  // this isn't strictly true, ya know...

- (BOOL)isCompoundCommand { return YES; } // this isn't strictly true, ya know...

- (BOOL)hasBoundingBox { return YES; }  // this isn't strictly true, ya know...

- (float)lastSampleIsAt
{
   float  oldestSample = 0.0, newSample;
   int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  newSample = [(id <WWRenderable>)[ribCommandList objectAt:i] lastSampleIsAt];
     if (newSample > oldestSample)
     {  oldestSample = newSample;
     }
  }

  newSample = [descendant lastSampleIsAt];
  if (newSample > oldestSample)
  {  oldestSample = newSample;
  }

  newSample = [nextPeer lastSampleIsAt];
  if (newSample > oldestSample)
  {  oldestSample = newSample;
  }

  return oldestSample;
}

- (unsigned long int)maxSampleBandwidth;
{
   unsigned long int  maxSampleBandwidth = 50;  //WAVE FIX ME
   int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  maxSampleBandwidth += [(id <WWRenderable>)[ribCommandList objectAt:i] maxSampleBandwidth];
  }

  maxSampleBandwidth += [descendant maxSampleBandwidth];
  maxSampleBandwidth += [nextPeer maxSampleBandwidth];

  return maxSampleBandwidth;
}


- (BOOL)isMoot
{
  return NO;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }

- (BOOL)theSameAs:otherRIBCommand
{
  return NO;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}


- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];

  return self;
}


- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
  int      i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns];

  return self;
}



- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];

  return self;
}


- renderMaps:(WW3DCamera *)camera
{
  int      i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  [descendant renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  [nextPeer renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];

  return self;
}




- (BOOL)pushesOrPopsCTM { return NO; }
- (BOOL)pushesCTM { return NO; }
- (BOOL)popsCTM { return NO; }


- addChild:newChild
{
  id  oldDescendant = nil,
      oldDescendantList = [[List alloc] init],
      parentShape = self;


  // I think I want to unlink the old descendant, link the child in as
  // the new one, and then make the oldDescendant the peer of the child.
  oldDescendant = [parentShape descendant];
  if (oldDescendant)
  {  while (oldDescendant) 
     {  [oldDescendantList addObject:oldDescendant];
        [oldDescendant unlink];
        oldDescendant = [parentShape descendant];
     }
  }
  [parentShape linkDescendant:newChild];
  if (oldDescendantList)
  {  oldDescendant = [oldDescendantList objectAt:0];
     while (oldDescendant) 
     {  [newChild linkPeer:oldDescendant];
        [oldDescendantList removeObjectAt:0];
        oldDescendant = [oldDescendantList objectAt:0];
     }
     [oldDescendantList free];
  }
  dirtyBoundingBox = YES;

  return self;
}


- siblings
{ 
  id  currentPeer;


  // Really should cache this by trapping all the hierarchy frobbing and
  // having a dirty bit set.  For right now, recalculate each time.

  [siblingList empty];

  if (self != [self firstPeer])
  {  [siblingList addObject:[self firstPeer]];
     currentPeer = [self firstPeer];

     while ([currentPeer nextPeer])
     {  [siblingList addObject:[currentPeer nextPeer]];
        currentPeer = [currentPeer nextPeer];
     }
  }

  return siblingList;
}

- children 
{ 
  id  currentPeer;


  // Really should cache this by trapping all the hierarchy frobbing and
  // having a dirty bit set.  For right now, recalculate each time.

  [childList empty];

  [childList addObject:[self descendant]];
  currentPeer = descendant;
  // note that the descendant is the firstPeer of it's siblings...
  while ([currentPeer nextPeer])
  {  [childList addObject:[currentPeer nextPeer]];
     currentPeer = [currentPeer nextPeer];
  }

  return childList;
}

- parent { return ancestor; }

- (unsigned short)pathSeparator { return pathSeparator; }
- setPathSeparator:(unsigned short)newSeparator { pathSeparator = newSeparator; return self; }


- getChildGivenPath:(const char *)path
{
  char  *part = (char *)path;
  int   cnt, howMany, i;
  id    ret;


  // the string looks something like "/foo/bar/zap/zow"
  // if the whole string is "/", return yourself
  // if the first part doesn't match your name, return nil.
  // if what's left after the first part is nil, return self;
  // if you're still here, send the message to each child until it doesn't return nil.  
  // if you get through all your children and it still returns nil, return nil.

  if ((strlen(path) == 1) && ((unsigned short)(*part) == pathSeparator))
  {  return self;
  }

  while (*part && ((unsigned short)(*part) != pathSeparator)) { part++; } 
  // from "/foo/bar" to "/bar" or from "/foo" to ""
  cnt = part - (path + 1);
  if (!cnt) {  return nil; } // catch the "/foo" to "" case

  if (!strncmp((path+1), [self shapeName], cnt))
  {  // the first part is my name
     if (!*part) { return self; }// if there isn't anything else, I'm who they're looking for

     // otherwise, send message to each child to see if it's them
     howMany = [childList count];
     i = 0;
     while (i < howMany)
     {  ret = [[childList objectAt:i] getChildGivenPath:part];
        if (ret)
        {  return ret;
        }
        i++;
     }
  }
  // that isn't my name at the beginning - this isn't for me or any of my kids
  return nil;
}

- (char *)getPath
{
  char  *path, pathSeparatorString[2]; 
  int   cnt = 1, 
        i;
  id    theShape;
  List  *myShapeList;


  myShapeList = [[List alloc] init];
  theShape = self;
  while (theShape)
  {  cnt += 2 + strlen([theShape shapeName]);
     [myShapeList addObject:theShape];
     theShape = [theShape ancestor];
  }

  path = malloc(cnt);
  if (!path)
  {  [myShapeList free];
     return NULL;
  }

  path[0] = '\0';
  sprintf(pathSeparatorString, "%c", pathSeparator);
  for (i = [myShapeList count] - 1; i >= 0; i--)  
  {  strcat(path, pathSeparatorString);
     strcat(path, [[myShapeList objectAt:i] shapeName]);
  }
  
  return path;
}

- preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  int      i, howMany = [ribCommandList count];


  for (i = 0; i < howMany; i++)
  {  [(id <WWRenderable>)[ribCommandList objectAt:i] preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  return self;
}


- preRenderSelf:(WW3DCamera *)camera
{
  int  i, howMany = [ribCommandList count];
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  for (i = 0; i < howMany; i++)
  {  [(id <WWAnimatable>)[ribCommandList objectAt:i] preRenderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
  }
  return self;
}

- (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStartTime endingAt:(RtFloat)intervalEndTime
{ 
   if (dirtyBoundingBox) 
   {  [self calculateBoundingBoxStartingAt:intervalStartTime endingAt:intervalEndTime];
   }
   return &boundingBox; 
}


- (int)findBoundingBoxFromRIBCommands:(int)startingIndex using:attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtBound   tmpBoundingBox;
  RtMatrix  tmpCTM;
  int       i = startingIndex,
            incr,
            howMany = [ribCommandList count];
  id        cmd;
  id        newAttributeState;


  // first we need to find a bounding box to start with
  while (i < howMany)
  {  cmd = [ribCommandList objectAt:i];

     // if the cmd pops the attribute stack, we're done.
     // we return how many commands we've gone through in our time here
     if ([cmd popsCTM])
     {  return (i - startingIndex);
     }

     if ([cmd pushesCTM])  // here we go again...
     {  newAttributeState = [[WW3DAttributeState alloc] init];
        i++;
        incr = [self findBoundingBoxFromRIBCommands:i using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        // we now need to merge this sub-attribute block into our current one
        if ([newAttributeState hasBoundingBox])
        {  // This means that the bounding box in newAttributeState needs to be
           // transformed by the ctm of attributeState, and then  we need to 
           // "grow" to the current bound with that tranformed sub-bound.

           [attributeState getTransformMatrix:tmpCTM];
          WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, [newAttributeState boundingBox], tmpCTM);
          [attributeState growBoundingBox:&tmpBoundingBox];
       }
       else
       {  NXLogError("warning: WW3DLight <%s> has a moot set of commands at indices %d to %d\n", [self shapeName], (i - 1), (i + incr - 1));
          NXLogError("\tif there was a LightSource or AreaLight source in there, you can ignore this warning, though...\n");
       }
       i += incr;
     }
     else 
     {  [cmd transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
        i++;
     }

    if ([cmd hasBoundingBox])
    {  // great! we grow the bound of the current attribute state
       [attributeState growBoundingBox:[cmd boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
    }

  }
  return (i - startingIndex);
}


- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime
{
  RtBound   tmpBoundingBox;
  RtMatrix  childMatrix, tmpCTM;
  int       i, startingChildIndex = 0, howManyKids;
  id        child, kids, newAttributeState;


  // this one is tricky.  we'll need to be smart about this...

  // basically, each RIBCommand has it's own bounding box.  We ask
  // each one in turn for it's boundingBox, and use that to build up ours.
  // Remember that each command is smart enough to know if any of it's
  // parameters have changed, but we need to be smart to realize if we have
  // rotated/translated/scaled.  Recall that for scaling and translating we
  // can just add that to the boundingBox without asking everyone to recalculate,
  // but rotating mucks up everything.  

  // so we need to transform these points against our 4x4, right?
  // Does the 3DKit take care of that for us?  Hmmm...

  // Actually, I don't think we need to cat our 4x4 against it, 
  // although I'm still unclear...  Anyway, what we really want to do is 
  // grovel over our ribCommandList and, to each object which answers YES
  // to hasBoundingBox we send the appropriate msg to get the bounding box
  // and compare it against ours.

  // actually, one more difficulty is that even though we might not
  // have any geometry (i.e. have any RIBCommands that respond 
  // positively to -hasBoundingBox), we still might have some commands
  // transform the boundingBox, ie. do more than concatenate an identity 
  // matrix against the matrix passed into -transform:.

  // what if you had 3 commands, the first two of which didn't have bounding boxes.
  // first time through, i == 1 at the end
  // next time, i == 2 at the end
  // final time through, i doesn't get incremented, but we are done
  // actually, it's more complicated than that.
  // some of these RIBCommands might not have a bounding box, but they do transform the CTM
  // also, you might have some transformational commands, then some geometry, then more
  // transformational commands, then geometry, then some more transformational... These
  // would all effect the children shapes in toto, but would also be affecting the 
  // bounding box of the various geometry commands.
  // urgh...

  // It's not really so bad.  Everything is taking place in the space of this shape, which 
  // means that this shape's transformation matrix isn't consulted.  We start off with 
  // the identity matrix, and transform a tmp CTM (current transformation matrix) as we
  // move through the commands.

  // Actually, it's a bit trickier than you might think.  The problem
  // is that although we have no fear of dropping down into a child until
  // we get to the list of kids, we just might hit a RIBAttributeBegin,
  // RIBTransformBegin, or RIBSolidBegin object, which would have the same effect
  // So, it looks like we need to extend the WWRenderable protocol to let us know
  // if the object pushes or pops the transformation stack.  We'll need to recurse
  // down the transformation tree, building up the CTM, and transforming any bounding
  // box we find.  We start off when the command pushes the stack.  We cons up a new
  // CTM and set it to the Identity matrix.  We then start walking down the commands,
  // first asking the command if it has a boundingBox.  If it does, we set our bound
  // to it, and then transform the points in the bound by our CTM.  We
  // then continue walking down the tree, asking each command if has a
  // bounding box, and if does, transforming that box by the CTM, and then
  // comparing it to our current bound, and growing it accordingly.  If the
  // command doesn't have a bound, we just transform our CTM and our
  // current bound by it.  We continue this until we get find a command
  // which pops the attribute stack.  If, along the way, we hit a command which
  // actually pushes the stack, we need to recurse down and call this routine again.
  // The routine should be passed, and return, the id of a list of WW3DAttributeState
  // objects, which have a bounding box, a CTM, etc. in them.

  newAttributeState = [[WW3DAttributeState alloc] init];
  i = [self findBoundingBoxFromRIBCommands:0 using:newAttributeState startingAt:shutterOpenTime endingAt:shutterCloseTime];
  if (i != [ribCommandList count])
  {  NXLogError("warning: shape <%s> has an unbalanced attribute delimiter at command %d (expected %d)\n", 
                [self shapeName], i, [ribCommandList count]);
     return nil;
  }

  [newAttributeState getTransformMatrix:tmpCTM];

  if (![newAttributeState hasBoundingBox])  // this Shape has no real geometry in it, but it might have kids...
  {  // we still need some approximation to start off with for this shape's bounding box.
     // to get it, we use our first child's bounding box.
     // if we don't have any kids, we just return.
     kids = [self children];
     if ([kids count] < 1)
     {  dirtyBoundingBox = NO;
        return self;
     }

     // note that it's fair to assume that if we have children, each one has a bounding
     // box.  If it didn't have a bounding box, that would mean it was a leaf node of
     // the tree without any geometry, which means we've got a moot node.
     child = [kids objectAt:0];

     // now, we can't just copy this bounding box, because it needs to
     // be transformed by the 4x4 that represents the transformation from this
     // shape to the child, i.e. it's 4x4.  
     N3D_CopyBound(*([child boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]), tmpBoundingBox);

     WW3DDetermineBoundGivenBoundAndCTM(&boundingBox, &tmpBoundingBox, tmpCTM);

     startingChildIndex = 1;
  }
  else
  {  N3D_CopyBound(*([newAttributeState boundingBox]), boundingBox);
  }

  // at this point, we only have a bounding box for the geometry
  // associated with this shape's geometry.  We now need to recurse down
  // our children, asking each to calculate it's bounding box.  We then
  // compare that bounding box with our own, and update ours accordingly.
  // note that if we didn't have any geometry in this shape, we've already
  // looked at the boundingBox of our first child, and set our boundingBox
  // to that, so startingChildIndex has been incremented to 1, so we
  // don't waste time redoing that.
  kids = [self children];
  howManyKids = [kids count];
  for (i = startingChildIndex; i < howManyKids; i++)
  {  child = [kids objectAt:i];
     N3D_CopyBound(*([child boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]), tmpBoundingBox);

     // the boundingBox that we get from the child should already be transformed 
     // correctly, right?  Or do we also need to concat the child's transform
     // onto it?  Yes, I think we do need to.  We get the bound, convert it to points,
     // transform it against the child's CTM, then multiply *that* by the
     // tmpCTM (which takes into account transformational RIBCommands in this
     // shape, which will get executed before the child shape, and then
     // convert those points back to a bound.  We can check that against 
     // the current bound.

     // we need to transform that bounding box to reflect the transformational 
     // commands that precede it in this shape's ribCommandList
     [child getTransformMatrix:childMatrix];
     WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, &tmpBoundingBox, childMatrix);
     WW3DDetermineBoundGivenBoundAndCTM(&tmpBoundingBox, &tmpBoundingBox, tmpCTM);

     // now we should all be in the same space; compare against current boundingBox
     //// X
     if (tmpBoundingBox[0] < boundingBox[0])
     {  boundingBox[0] = tmpBoundingBox[0];
     }
     if (tmpBoundingBox[1] > boundingBox[1])
     {  boundingBox[1] = tmpBoundingBox[1];
     }
     //// Y
     if (tmpBoundingBox[2] < boundingBox[2])
     {  boundingBox[2] = tmpBoundingBox[2];
     }
     if (tmpBoundingBox[3] > boundingBox[3])
     {  boundingBox[3] = tmpBoundingBox[3];
     }
     //// Z
     if (tmpBoundingBox[4] < boundingBox[4])
     {  boundingBox[4] = tmpBoundingBox[4];
     }
     if (tmpBoundingBox[5] > boundingBox[5])
     {  boundingBox[5] = tmpBoundingBox[5];
     }
  }

  dirtyBoundingBox = NO;

  return self;
}

// this is a private method, should only used by an instance on itself
- setBoundingBox:(RtBound *)newBoundingBox { return self; }


- (BOOL)matricesAreEqual:(RtMatrix)m1 :(RtMatrix)m2
{
  if (m1[0][0] != m2[0][0])  {  return NO; }
  if (m1[0][1] != m2[0][1])  {  return NO; }
  if (m1[0][2] != m2[0][2])  {  return NO; }
  if (m1[0][3] != m2[0][3])  {  return NO; }

  if (m1[1][0] != m2[1][0])  {  return NO; }
  if (m1[1][1] != m2[1][1])  {  return NO; }
  if (m1[1][2] != m2[1][2])  {  return NO; }
  if (m1[1][3] != m2[1][3])  {  return NO; }

  if (m1[2][0] != m2[2][0])  {  return NO; }
  if (m1[2][1] != m2[2][1])  {  return NO; }
  if (m1[2][2] != m2[2][2])  {  return NO; }
  if (m1[2][3] != m2[2][3])  {  return NO; }

  if (m1[3][0] != m2[3][0])  {  return NO; }
  if (m1[3][1] != m2[3][1])  {  return NO; }
  if (m1[3][2] != m2[3][2])  {  return NO; }
  if (m1[3][3] != m2[3][3])  {  return NO; }

  return YES;
}


- (BOOL)isIdentityMatrix:(RtMatrix)m  { return [self matricesAreEqual:(RtFloat (*)[4])N3DIdentityMatrix :m]; }

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape {%s} ", [self shapeName]); 
   [self getTransformMatrix:aMatrix];
   if ([self isIdentityMatrix:aMatrix])
   {  NXPrintf(stream, ";\n");
   }
   else  // need to add the transformation matrix, since it's not the identity matrix...
   {  NXPrintf(stream, "{");
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
      // it would be nice if we put these on separate lines, but line up...
      // how far in do we need to go? well, first we need to tab the right amount, and then
      // move over "startShape " + strlen([self shapeName]) + "{"....
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");

      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
   
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f};\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      NXPrintf(stream, "\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeEve:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd writeEve:stream atTabLevel:(tab+1)];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeEve: itself...
   [descendant writeEve:stream atTabLevel:(tab + 1)];
   NXPrintf(stream, "\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "endShape\n"); 

   // now tell my nextPeer to writeEve: itself...
   [nextPeer writeEve:stream atTabLevel:tab];

   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape {%s} ", [self shapeName]); 
   [self getTransformMatrix:aMatrix];
   if ([self isIdentityMatrix:aMatrix])
   {  NXPrintf(stream, ";\n");
   }
   else  // need to add the transformation matrix, since it's not the identity matrix...
   {  NXPrintf(stream, "{");
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
      // it would be nice if we put these on separate lines, but line up...
      // how far in do we need to go? well, first we need to tab the right amount, and then
      // move over "startShape " + strlen([self shapeName]) + "{"....
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");

      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
   
      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

      for (i = 0; i < tab; i++)
      {  NXPrintf(stream, "\t");
      }
      for (i = 0; i < howManySpaces; i++)
      {  NXPrintf(stream, " ");
      }
      NXPrintf(stream, "%f %f %f %f};\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      NXPrintf(stream, "\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeScene:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd writeScene:stream atTabLevel:(tab+1)];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeScene: itself...
   [descendant writeScene:stream atTabLevel:(tab + 1)];
   NXPrintf(stream, "\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "endShape\n"); 

   // now tell my nextPeer to writeScene: itself...
   [nextPeer writeScene:stream atTabLevel:tab];

   return self;
}


- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   RtMatrix  aMatrix;
   int       i, 
             howManySpaces, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   NXPrintf(stream, "startShape WW3DShape; ");
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
     NXPrintf(stream, "startShape %s ", [self shapeName]); 
     [self getTransformMatrix:aMatrix];
     if ([self isIdentityMatrix:aMatrix])
     {  NXPrintf(stream, " } left;\n");
     }
     else  // need to add the transformation matrix, since it's not the identity matrix...
     {  NXPrintf(stream, "{");
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
        // it would be nice if we put these on separate lines, but line up...
        // how far in do we need to go? well, first we need to tab the right amount, and then
        // move over "startShape " + strlen([self shapeName]) + "{"....
        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        howManySpaces = strlen("startShape ") + strlen([self shapeName]) + strlen(" {");
  
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...

        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f ",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
        NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see...
  
        for (i = 0; i < tab; i++)
        {  NXPrintf(stream, "\t");
        }
        for (i = 0; i < howManySpaces; i++)
        {  NXPrintf(stream, " ");
        }
        NXPrintf(stream, "%f %f %f %f}} left;\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
        NXPrintf(stream, "\n");
     }
   NXPrintf(stream, "}\n");
  
   index++;
   // okay, next object - it will be a child of this shape:
   if (surfaceShader)
   {  [surfaceShader write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      [aCmd write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
      NXPrintf(stream, "\n");
   }

   // now tell my descendant to writeEve: itself...
   [descendant write3DTextScene:stream atTabLevel:(tab+1) index:index++ time:time until:lastTime];
   NXPrintf(stream, "\n");

   // okay, now finish this text shape off by putting out the endShape

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "startShape endShape; ");
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
     NXPrintf(stream, "endShape");
   NXPrintf(stream, "} left;}\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
  NXPrintf(stream, "endShape;\n");

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
  NXPrintf(stream, "endShape;\n");  // okay, end off the WW3DShape


   // now tell my nextPeer to writeEve: itself...
   [nextPeer write3DTextScene:stream atTabLevel:(tab+1) index:index time:time until:lastTime];

   return self;
}


- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   RtMatrix  aMatrix;
   int       i, newTab, howMany = [ribCommandList count];
   id        aCmd;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# startShape {%s}\n", [self shapeName]);
   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "DEF \"%s\" Separator {\n", [self shapeName]);
   [self getTransformMatrix:aMatrix];
   if (![self isIdentityMatrix:aMatrix])
   {  

      for (i = 0; i < (tab+1); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "MatrixTransform {\n");
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "matrix %f %f %f %f\n", aMatrix[0][0], aMatrix[0][1], aMatrix[0][2], aMatrix[0][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n",  aMatrix[1][0], aMatrix[1][1], aMatrix[1][2], aMatrix[1][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n",  aMatrix[2][0], aMatrix[2][1], aMatrix[2][2], aMatrix[2][3]);
      for (i = 0; i < (tab+2); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "       %f %f %f %f\n", aMatrix[3][0], aMatrix[3][1], aMatrix[3][2], aMatrix[3][3]);
      for (i = 0; i < (tab+1); i++)
      {  NXPrintf(stream, "\t");
      }
      NXPrintf(stream, "}\n");
   }
   // WAVE!!! Need to write out the rest of my shaders here!!!
   if (surfaceShader)
   {  [surfaceShader writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }
   if (displacementShader)
   {  [displacementShader writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   // okay, now tell all my rib commands to write themselves out...
   newTab = tab + 1;
   for (i = 0; i < howMany; i++)
   {  aCmd = [ribCommandList objectAt:i];
      if (![aCmd isMootStartingAt:currentTime endingAt:currentTime])
      {  if ([aCmd popsCTM])
         {  newTab--;
         }
         [aCmd writeInventorAtTime:currentTime to:stream atTabLevel:newTab];
         if ([aCmd pushesCTM])
         {  newTab++;
         }
         NXPrintf(stream, "\n");
      }
   }

   // now tell my descendant to writeInventor: itself...
   aCmd = [ribCommandList objectAt:i];
   if (![aCmd isMootStartingAt:currentTime endingAt:currentTime])
   {  [descendant writeInventorAtTime:currentTime to:stream atTabLevel:(tab + 1)];
      NXPrintf(stream, "\n");
   }

   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "}\n"); 

   // now tell my nextPeer to writeInventor: itself...
   [nextPeer writeInventorAtTime:currentTime to:stream atTabLevel:tab];

   return self;
}


#define typeVector "fcfffffffff"
#define typeValues &visibleSizeScale, &visibleWhenPrinting, \
                   &a, &b, &c, &d, &vMagnitude, &cosOfAlpha, &sinOfAlpha, &xRotate, &yRotate
		   
- read:(NXTypedStream *)stream 
{
    int    version;
    
    [super read:stream];
    
    NX_DURING
	version = NXTypedStreamClassVersion(stream, "WW3DLight");
	if (version == 1) 
        {  NXReadTypes(stream, typeVector, typeValues);
	   NXReadArray(stream, "f", 3, v);
	   NXReadArray(stream, "f", 3, u);
	   NXReadArray(stream, "f", 3, uPrime);
	   NXReadArray(stream, "f", 3, innerConeOpacity);
	   NXReadArray(stream, "f", 3, outerConeOpacity);
	   NXReadArray(stream, "f", 3, beamOpacity);
	}
    NX_HANDLER
	NXLogError("in read: %s, exception [%d] raised.\n", [[self class] name], NXLocalHandler.code);
	return nil;
    NX_ENDHANDLER
    return self;
}

- write:(NXTypedStream *)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteArray(stream, "f", 3, v);
    NXWriteArray(stream, "f", 3, u);
    NXWriteArray(stream, "f", 3, uPrime);
    NXWriteArray(stream, "f", 3, innerConeOpacity);
    NXWriteArray(stream, "f", 3, outerConeOpacity);
    NXWriteArray(stream, "f", 3, beamOpacity);
    return self;
}

// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }

@end
